package main

import (
	"context"
	"encoding/gob"
	"flag"
	"fmt"
	"net"
	"net/rpc/jsonrpc"
	"os"
	"src/clientsession"
	"src/config"
	"src/log"
	"src/message"
	"src/rpcctrl"
	"src/sessionadmin"
	"time"
)

func init() {
	log.SetPrefix("proxy_client:")
}

// Client client
type Client struct {
	addr      string
	port      int
	maxBufLen int
	proxyPort int
	desc      string
	conn      net.Conn
	decoder   *gob.Decoder
	encoder   *gob.Encoder
	writech   chan *message.Message
	admin     *sessionadmin.Admin
}

func (c *Client) sendMsg(msg *message.Message) {
	if c.writech != nil {
		c.writech <- msg
	}
}

func (c *Client) goWriteconn(ctx context.Context) {
	for {
		select {
		case msg, ok := <-c.writech:
			if !ok {
				fmt.Println("c.writech close .......")
				return
			}
			err := c.encoder.Encode(msg)
			//如果错误就直接退出
			if err != nil {
				log.Println("conn encode write failed and go exit")
				return
			}
		case <-ctx.Done():
			log.Println("ctx notify exit goWriteconn now")
			return
		}
	}
}

func (c *Client) sendhello() {
	hostname, _ := os.Hostname()
	msg := new(message.Message)

	hello := fmt.Sprintf("%s (%s:%d) is comming %s!!!", hostname, c.desc, c.proxyPort, c.conn.LocalAddr().String())
	msg.Cmd = message.MsgHello
	msg.Data = hello

	c.sendMsg(msg)
}

func (c *Client) readdata() (*message.Message, error) {
	msg := new(message.Message)
	err := c.decoder.Decode(msg)

	return msg, err
}

//创建代理会话
func (c *Client) transitSessionCreate(server string) {
	//连接服务器
	connServer, err := net.Dial("tcp", server)
	if err != nil {
		log.Println("conn server", server, "failed")
		return
	}

	log.Println("conn ", connServer.RemoteAddr().String(), "success and go connect proxyport=", c.proxyPort)
	//连接ssh
	dstsh := fmt.Sprintf(":%d", c.proxyPort)
	connProxy, err2 := net.Dial("tcp", dstsh)
	if err2 != nil {
		log.Println("conn proxy=", dstsh, "failed and close connServer")
		connServer.Close()
		return
	}
	cs := clientsession.GetNewClientSession(c.admin.GetID(), connServer, connProxy, c.maxBufLen)
	cs.Init()
	c.admin.AddSession(cs)
}

func (c *Client) netCmdProc(msg *message.Message) bool {
	switch msg.Cmd {
	case message.MsgDevServerNotify:
		log.Println("get MsgDevServerNotify =", string(msg.Data))
		//去创建中转会话
		c.transitSessionCreate(msg.Data)
	default:
		log.Println("err cmd=", msg.Cmd)
	}

	return true
}

func (c *Client) readProcLoop(ctx context.Context) {
	log.Println("goto readProcLoop")
	for {
		log.Println("client goto readdata")
		msg, err := c.readdata()
		if err != nil {
			log.Println("readdata failed err=", err)
			break
		}

		if !c.netCmdProc(msg) {
			log.Println("netCmdProc failed")
			break
		}
	}
	log.Println("exit readProcLoop")
}

func (c *Client) sendkeepalive() {
	msg := new(message.Message)
	msg.Cmd = message.MsgKeepalive
	msg.Data = "this is keeepalive"

	c.sendMsg(msg)
}

func (c *Client) show() {
	log.Println("")
	log.Println("goto client session show **********************************")
	if c.conn != nil {
		log.Println("conn remote addr", c.conn.RemoteAddr().String())
	}
	c.admin.Show()
}

func (c *Client) syncSession() {
	c.admin.SyncSession()
}

func (c *Client) goRunTicker(ctx context.Context) {
	//定时心跳
	keepalivetimer := time.NewTicker(5 * time.Second)
	//定时显示
	showtimer := time.NewTicker(5 * time.Second)
	//定时删除废弃会话
	syncsessiontimer := time.NewTicker(5 * time.Second)

	for {
		select {
		case <-ctx.Done():
			log.Println("ctx notify exit goRunTicker now")
			goto exit
		case <-keepalivetimer.C:
			c.sendkeepalive()
		case <-showtimer.C:
			c.show()
		case <-syncsessiontimer.C:
			c.syncSession()
		}
	}

exit:
	keepalivetimer.Stop()
	showtimer.Stop()
	syncsessiontimer.Stop()
}

func (c *Client) init() {
	log.Println("enter client init")
	c.decoder = gob.NewDecoder(c.conn)
	c.encoder = gob.NewEncoder(c.conn)
	c.writech = make(chan *message.Message, 3)
	c.admin = sessionadmin.GetNewAdmin()
}

func (c *Client) runloop() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	log.Println("enter client run")
	go c.goWriteconn(ctx)
	go c.goRunTicker(ctx)
	c.sendhello()
	c.readProcLoop(ctx)
	//先通知关协程
	cancelFunc()
	c.close()
}

func (c *Client) close() {
	log.Println("enter client close")
	close(c.writech)
	c.writech = nil
	if c.conn != nil {
		c.conn.Close()
		c.conn = nil
	}
	c.admin.Close()
	c.decoder = nil
	c.encoder = nil
}

var (
	addr          string
	port          int
	rpcPort       int
	proxyPort     int
	h             bool
	g             bool
	miscCmd       string
	desc          string
	outputLogPath string
)

func usage() {
	fmt.Fprintf(os.Stderr, `proxy_client version: 1.0
	Usage: proxy_client [-h] [-g] [-serveraddr "0.0.0.0"] [-serverport 0] [-proxyPort 0] [-desc "desc"] 
	[-logfile "log"] [-rpcport 6001] [-cmd cmd]
	
	Options:
	`)
	flag.PrintDefaults()
}

func (c *Client) argParse() bool {
	flag.BoolVar(&h, "h", false, "this help")
	flag.BoolVar(&g, "g", false, "从服务器获取相关信息")
	flag.StringVar(&miscCmd, "cmd", "getinfo", "从服务器获取相关信息命令")
	flag.StringVar(&addr, "serveraddr", config.ServerListenAddr, "代理服务器地址")
	flag.IntVar(&rpcPort, "rpcport", config.ServerRPCPort, "rpc服务器监听端口")
	flag.StringVar(&desc, "desc", "", "客户端描述")
	flag.IntVar(&port, "serverport", config.ServerListenPort, "代理服务器端口")
	flag.IntVar(&proxyPort, "proxyPort", config.ProxyPort, "代理port")
	flag.StringVar(&outputLogPath, "logfile", "", "输出日志文件")

	flag.Usage = usage

	flag.Parse()
	if h {
		flag.Usage()
		return false
	}
	if outputLogPath != "" {
		log.SetOutputPathFile(outputLogPath)
	}

	c.maxBufLen = config.MaxBufLen
	c.addr = addr
	c.port = port
	c.proxyPort = proxyPort
	c.desc = desc

	return true
}

func doMiscProc() {
	conn, err := jsonrpc.Dial("tcp", fmt.Sprintf("%s:%d", addr, rpcPort))
	if err != nil {
		panic(err)
	}
	req := rpcctrl.Reqest{miscCmd}
	var res rpcctrl.Response

	err = conn.Call("RPCCtrl.Get", req, &res)
	if err != nil {
		panic(err)
	}
	log.Println("get info:", res.Info)
}

func main() {
	var client Client

	if !client.argParse() {
		return
	}

	//做些命令获取命令操作
	if g {
		doMiscProc()
		return
	}

	//windows上隐藏容器后台运行
	//go build -ldflags -H=windowsgui

	dstServer := fmt.Sprintf("%s:%d", client.addr, client.port)
	//程序不主动退出
	for {
		conn, err := net.Dial("tcp", dstServer)
		if err != nil {
			log.Println(err)
			log.Println("and wait next!!!!!!!!!!!!!!!")
			time.Sleep(time.Second * 10)
			continue
		}
		client.conn = conn
		client.init()
		client.runloop()
		log.Println("goto connect next time!!!!!!!!!!!!!!!!!!!!!!!!!!")
	}
}
